import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectConnection, InjectRepository } from '@nestjs/typeorm';
import { Connection, getConnection, In, Repository } from 'typeorm';
import { PlayerSignup } from '../entities/player-signup.entity';
import { CreatePlayerSignupDto } from '../dto/create-player-signup.dto';
import {
  DoubleEventList,
  EventStatusEnum,
  femaleEventList,
  maleEventList,
  playerSignupTypeEnum,
  TeamEventList,
} from 'src/common/constant/enums';
import { Event } from '../../event/entities/event.entity';
import logger from 'src/common/utils/log';
import dayjs from 'dayjs';
import { EventService } from '../../event/event.service';
import { generateUniqueId } from 'src/common/utils/utils';
import { groupBy } from 'lodash';
import { CreatePairDto, UpdatePairDto } from '../dto/create-pair.dto';
import { CreateTeamSignupDto } from '../dto/create-team.dto';
import {
  SearchPairResponseDto,
  SearchPlayerResponseDto,
} from '../dto/search-player.dto';
import { ResponsePairDto } from '../dto/response-pair.dto';
import { ResponsePlayerSignupDto } from '../dto/response-player-signup.dto';

@Injectable()
export class PlayerSignupService {
  constructor(
    @InjectRepository(PlayerSignup)
    private playerSignupRepository: Repository<PlayerSignup>,
    @InjectRepository(Event)
    private eventRepository: Repository<Event>,
    private eventService: EventService,
    @InjectConnection() private readonly connection: Connection,
  ) {}

  async create(playerSignupDto: CreatePlayerSignupDto): Promise<PlayerSignup> {
    const playerSignup = this.playerSignupRepository.create(playerSignupDto);
    return await this.playerSignupRepository.save(playerSignup);
  }

  async findAll(): Promise<PlayerSignup[]> {
    return await this.playerSignupRepository.find();
  }

  async findOne(id: number): Promise<PlayerSignup> {
    return await this.playerSignupRepository.findOne({ where: { id } });
  }

  async findByIds(ids: number[]): Promise<PlayerSignup[]> {
    return await this.playerSignupRepository.find({ where: { id: In(ids) } });
  }
  async findByIdsAndEventId(ids: number[], eventId: number) {
    const keys = await this.playerSignupRepository
      .createQueryBuilder('playerSignup')
      .select('playerSignup.key')
      .where('playerSignup.id In (:...ids)', { ids })
      .getMany();
    return await this.playerSignupRepository
      .createQueryBuilder('playerSignup')
      .where('playerSignup.key In (:...keys)', {
        keys: keys.map((item) => item.key),
      })
      .andWhere('playerSignup.eventId = :eventId', { eventId })
      .getMany();
  }
  async update(
    id: number,
    playerSignupDto: CreatePlayerSignupDto,
  ): Promise<PlayerSignup> {
    await this.playerSignupRepository.update(id, playerSignupDto);
    return this.findOne(id);
  }

  async remove(id: number): Promise<void> {
    await this.playerSignupRepository.delete(id);
  }

  async removeInIds(ids: number[]) {
    return await this.playerSignupRepository.delete(ids);
  }
  async removeIds(ids: number[]) {
    return await this.removeInIds(ids);
  }

  async removeByKey(key: string): Promise<void> {
    await this.playerSignupRepository
      .createQueryBuilder()
      .delete()
      .where('key = :key', { key })
      .execute();
  }

  async saveList(
    playerSignupDtos: CreatePlayerSignupDto[],
  ): Promise<PlayerSignup[]> {
    const playerSignups = this.playerSignupRepository.create(playerSignupDtos);
    return await this.playerSignupRepository.save(playerSignups);
  }

  async listByEventId(eventId: number): Promise<PlayerSignup[]> {
    return await this.playerSignupRepository
      .createQueryBuilder('playerSignup')
      .leftJoinAndSelect('playerSignup.subPlayerSignups', 'subPlayerSignups')
      .leftJoinAndSelect('playerSignup.event', 'event')
      .leftJoinAndSelect('subPlayerSignups.event', 'subEvent')
      .leftJoinAndSelect('event.stages', 'stages')
      .leftJoinAndSelect('subEvent.stages', 'subStages')
      .leftJoinAndSelect('playerSignup.stageEvent', 'stageEvent')
      .where('playerSignup.eventId = :eventId', { eventId })
      .andWhere('playerSignup.parentPlayerId IS NULL')
      .getMany();
  }

  async ListByEventIdForTopPlayer(eventId: number): Promise<PlayerSignup[]> {
    return await this.playerSignupRepository
      .createQueryBuilder('playerSignup')
      .leftJoinAndSelect('playerSignup.subPlayerSignups', 'subPlayerSignups')
      .where('playerSignup.eventId = :eventId', { eventId })
      .andWhere('playerSignup.parentPlayerId IS NULL')
      .getMany();
  }

  async knockoutPlayers(eventId: number, idArray: number[]) {
    await this.playerSignupRepository
      .createQueryBuilder()
      .update(PlayerSignup)
      .set({ isMainPlayer: false })
      .where('fk_eventId = :eventId', { eventId })
      .andWhere('id NOT IN (:...idArray)', { idArray })
      .execute();
  }
  async removeTeamPlayers(keys: string[]) {
    logger.info(`start removeTeamPlayer :${keys.join(',')}`);
    const playerSignups = await this.playerSignupRepository
      .createQueryBuilder('playerSignup')
      .where('playerSignup.key IN (:...keys)', { keys })
      .getMany();
    // 清空依赖运动员的团队数据
    await this.saveList(
      playerSignups.map((v) => {
        v.subPlayerSignups = [];
        return v;
      }),
    );
    // 删除
    await this.removeInIds(playerSignups.map((v) => v.id));
    logger.info(`finish removeTeamPlayer`);
  }
  async handleTeamPlayer(createTeamSignupDto: CreateTeamSignupDto) {
    logger.info('start handleTeamPlayer');
    const { events, subPlayerSignups, key } = createTeamSignupDto;
    if (!events?.length || !subPlayerSignups.length) {
      throw new HttpException(
        '缺少events, subPlayerSignups',
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
    const playerKeys = subPlayerSignups.map((v) => v.key);
    const eventIds = events.map((v) => v.id);
    const singlePlayers =
      (await this.playerSignupRepository
        .createQueryBuilder('playerSignup')
        .where('playerSignup.key IN (:...playerKeys)', { playerKeys })
        .andWhere('playerSignup.eventId IN (:...events)', {
          events: eventIds,
        })
        .getMany()) || [];

    if (!singlePlayers.length) {
      throw new HttpException(
        `${playerKeys} 选手不存在`,
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
    const singlePlayersGroupByEventId = groupBy(
      singlePlayers,
      (v) => v.eventId,
    );
    let teamPlayers: Array<PlayerSignup> = [];
    if (key) {
      teamPlayers =
        (await this.playerSignupRepository
          .createQueryBuilder('playerSignup')
          .where('playerSignup.key = :key', { key })
          .getMany()) || [];

      if (!teamPlayers || !teamPlayers.length) {
        throw new HttpException(
          `teamPlayer ${key} 不存在`,
          HttpStatus.INTERNAL_SERVER_ERROR,
        );
      }
    }
    let signupEvents = [];
    if (eventIds?.length) {
      signupEvents =
        (await this.eventService.findWithCategoryAndTournamentByIds(
          eventIds,
        )) || [];
      if (eventIds.length < signupEvents.length) {
        const errorEventIds = [];
        eventIds.forEach((v) => {
          if (!signupEvents.find((_v) => _v.id === v)) {
            errorEventIds.push(v);
          }
        });
        throw new HttpException(
          `报名项目 ${errorEventIds.join(',')} 不存在`,
          HttpStatus.INTERNAL_SERVER_ERROR,
        );
      }
    }
    const newSignups: Array<PlayerSignup> = [];
    const updateSignups: Array<PlayerSignup> = [];
    const deleteSignups: Array<PlayerSignup> = [];
    const updateSinglePlayerSignups: Array<PlayerSignup> = [];
    // 1. 遍历events,如果user和event 对上，则更新，如果没有，则添加
    let saveKey;
    if (key) {
      saveKey = key;
    } else {
      saveKey = await this.genSinupKey();
    }

    signupEvents.forEach((event) => {
      const { id: eventId } = event;
      const signupThisEventTeamPlayer = teamPlayers.find(
        (v) => v.eventId === eventId,
      );
      if (!singlePlayersGroupByEventId[eventId]?.length) {
        throw new HttpException(
          `选手 ${playerKeys.join(',')} 未报名 ${eventId}`,
          HttpStatus.INTERNAL_SERVER_ERROR,
        );
      }
      if (signupThisEventTeamPlayer) {
        // 已经报名过,则更新
        const updatePlayerEntity = this.playerSignupRepository.create({
          ...signupThisEventTeamPlayer,
          ...createTeamSignupDto,
          key: saveKey,
          eventId,
          type: playerSignupTypeEnum.team,
          subPlayerSignups: singlePlayersGroupByEventId[eventId].map((v) => {
            return { id: v.id };
          }),
        });
        updateSinglePlayerSignups.push(
          // 更新subPlayerSignups的orgnisition，确保在导出时显组织名称显示团队名称
          ...singlePlayersGroupByEventId[eventId].map((v: PlayerSignup) => {
            v.organization = updatePlayerEntity.organization;
            return v;
          }),
        );
        // 验证是否能报名
        this.canPlayerSignupEvent(updatePlayerEntity, event);

        updateSignups.push(updatePlayerEntity);
      } else {
        const newPlayerEntity = this.playerSignupRepository.create({
          ...createTeamSignupDto,
          key: saveKey,
          eventId,
          type: playerSignupTypeEnum.team,
          subPlayerSignups: singlePlayersGroupByEventId[eventId].map((v) => {
            return { id: v.id };
          }),
        });
        updateSinglePlayerSignups.push(
          // 更新subPlayerSignups的orgnisition，确保在导出时显组织名称显示团队名称
          ...singlePlayersGroupByEventId[eventId].map((v: PlayerSignup) => {
            v.organization = newPlayerEntity.organization;
            return v;
          }),
        );
        this.canPlayerSignupEvent(newPlayerEntity, event);

        newSignups.push(newPlayerEntity);
      }
    });
    // 2. 遍历signupPlayers,如果不在events中，则删除
    teamPlayers.forEach((player) => {
      const { eventId } = player;
      if (!signupEvents.find((v) => v.id === eventId)) {
        deleteSignups.push(player);
      }
    });
    if (updateSinglePlayerSignups.length) {
      // 更新subPlayerSignups,
      await this.saveList(updateSinglePlayerSignups as any);
    }
    if (newSignups.length) {
      logger.info(`add new player ${newSignups.map((v) => v.key).join(',')}`);
      return await this.saveList(newSignups as any);
    }
    if (updateSignups.length) {
      logger.info(`update player ${updateSignups.map((v) => v.key).join(',')}`);
      return await this.saveList(updateSignups as any);
    }
    if (deleteSignups.length) {
      logger.info(`delete player ${deleteSignups.map((v) => v.key).join(',')}`);
      await this.saveList(
        deleteSignups.map((v) => {
          v.subPlayerSignups = [];
          return v;
        }),
      );
      await this.removeInIds(deleteSignups.map((v) => v.id));
    }
    logger.info('finish handleTeamPlayer');
  }
  async genSinupKey() {
    logger.info('start genSinupKey');
    let saveKey;
    let saveKeyPlayer;
    let checkTime = 0;
    while (checkTime === 0 || saveKeyPlayer) {
      checkTime++;
      saveKey = generateUniqueId();
      saveKeyPlayer = await this.playerSignupRepository.findOne({
        where: { key: saveKey },
      });
      if (checkTime > 3) {
        throw new HttpException(
          `生成uuid失败(3),请重试`,
          HttpStatus.INTERNAL_SERVER_ERROR,
        );
      }
    }
    logger.info(`finish genSinupKey ${saveKey}`);
    return saveKey;
  }
  async handleSinglePlayer(createPlayerSignupDto: CreatePlayerSignupDto) {
    const { key, events } = createPlayerSignupDto;
    if (!key && !events?.length) {
      throw new HttpException(
        '缺少key 或者 events',
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
    let signupPlayers: Array<PlayerSignup> = [];
    if (key) {
      signupPlayers =
        (await this.playerSignupRepository
          .createQueryBuilder('playerSignup')
          .where('playerSignup.key = :key', { key })
          .getMany()) || [];

      if (!signupPlayers || !signupPlayers.length) {
        throw new HttpException(
          `player ${key} 不存在`,
          HttpStatus.INTERNAL_SERVER_ERROR,
        );
      }
    }
    let signupEvents = [];
    if (events && events?.length) {
      const eventIds = events.map((v) => v.id);
      signupEvents =
        (await this.eventService.findWithCategoryAndTournamentByIds(
          eventIds,
        )) || [];
      if (eventIds.length > signupEvents.length) {
        const errorEventIds = [];
        eventIds.forEach((v) => {
          if (!signupEvents.find((_v) => _v.id === v)) {
            errorEventIds.push(v);
          }
        });
        throw new HttpException(
          `报名项目 ${errorEventIds.join(',')} 不存在`,
          HttpStatus.INTERNAL_SERVER_ERROR,
        );
      }
    }
    const newSignups: Array<PlayerSignup> = [];
    const updateSignups: Array<PlayerSignup> = [];
    const deleteSignups: Array<PlayerSignup> = [];
    // 1. 遍历events,如果user和event 对上，则更新，如果没有，则添加
    let saveKey;
    if (key) {
      saveKey = key;
    } else {
      saveKey = await this.genSinupKey();
    }

    signupEvents.forEach((event) => {
      const { id: eventId } = event;
      const signupThisEventPlayer = signupPlayers.find(
        (v) => v.eventId === eventId,
      );
      if (signupThisEventPlayer) {
        // 已经报名过,则更新
        const updatePlayerEntity = this.playerSignupRepository.create({
          ...signupThisEventPlayer,
          ...createPlayerSignupDto,
          type: playerSignupTypeEnum.single,
          key: saveKey,
          eventId,
        });
        // 验证是否能报名
        this.canPlayerSignupEvent(updatePlayerEntity, event);

        updateSignups.push(updatePlayerEntity);
      } else {
        const newPlayerEntity = this.playerSignupRepository.create({
          ...createPlayerSignupDto,
          type: playerSignupTypeEnum.single,
          key: saveKey,
          eventId,
        });
        this.canPlayerSignupEvent(newPlayerEntity, event);

        newSignups.push(newPlayerEntity);
      }
    });
    // 2. 遍历signupPlayers,如果不在events中，则删除
    signupPlayers.forEach((player) => {
      const { eventId } = player;
      if (!signupEvents.find((v) => v.id === eventId)) {
        deleteSignups.push(player);
      }
    });
    let resData = [];
    if (newSignups.length) {
      logger.info(`add new player ${newSignups.map((v) => v.key).join(',')}`);
      const saveData = await this.saveList(newSignups as any);
      resData = resData.concat(saveData);
    }
    if (updateSignups.length) {
      logger.info(`update player ${updateSignups.map((v) => v.key).join(',')}`);
      const saveData = await this.saveList(updateSignups as any);
      resData = resData.concat(saveData);
    }
    if (deleteSignups.length) {
      logger.info(`delete player ${deleteSignups.map((v) => v.key).join(',')}`);
      await this.removeInIds(deleteSignups.map((v) => v.id));
    }
    logger.info('end handleSinglePlayer');
    return resData;
  }
  async getPairDetail(categoryId: number, pairNames: string[]) {
    const queryBuilder = this.playerSignupRepository
      .createQueryBuilder('playerSignup')
      .where('playerSignup.type = :type', { type: playerSignupTypeEnum.single })
      .andWhere('playerSignup.pairName in (:...pairNames)', { pairNames })
      .andWhere(
        `playerSignup.eventId IN (
          ${this.eventRepository
            .createQueryBuilder('event')
            .select('event.id')
            .where('event.categoryId = :categoryId', { categoryId })
            .getQuery()}
        )`,
      )
      .leftJoinAndSelect('playerSignup.event', 'event')
      .setParameters({ categoryId, pairNames });
    return await queryBuilder.getMany();
  }
  async searchPairList(
    page,
    size,
    param: {
      categoryId: number;
      eventIds?: number[];
      playerName?: string;
    },
  ): Promise<[Array<PlayerSignup>, number]> {
    logger.info('start searchPairList');
    const { categoryId, eventIds, playerName } = param;
    if (!categoryId) {
      throw new HttpException(
        'categoryId is required',
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
    const getBaseQuery = () => {
      const queryBuilder = this.playerSignupRepository
        .createQueryBuilder('playerSignup')
        // .select('distinct playerSignup.pairName')
        .where('playerSignup.type = :type', {
          type: playerSignupTypeEnum.single,
        })
        .andWhere('playerSignup.pairName IS NOT NULL')
        .andWhere(
          `playerSignup.eventId IN (${
            eventIds.length
              ? eventIds
              : this.eventRepository
                  .createQueryBuilder('event')
                  .select('event.id')
                  .where('event.categoryId = :categoryId', { categoryId })
                  .getQuery()
          })`,
        )
        .setParameters({ categoryId });
      if (playerName) {
        queryBuilder.andWhere('playerSignup.pairName LIKE :playerName', {
          playerName: `%${playerName}%`,
        });
      }
      return queryBuilder;
    };
    const { count } = await getBaseQuery()
      .select('count(distinct playerSignup.pairName)', 'count')
      .getRawOne();
    // .select('playerSignup.pairName')
    // .distinct(true)
    // .take(size)
    // .skip((page - 1) * size)
    // .getManyAndCount();
    // logger.info(`after getPairDetail pairNames:${pairNames.length}`);
    // if (!pairNames.length) {
    //   return [[], 0];
    // }
    const pairNames = await getBaseQuery()
      .select('distinct playerSignup.pairName')
      .take(size)
      .skip((page - 1) * size)
      .getRawMany();
    const res = await getBaseQuery()
      .andWhere(`playerSignup.pairName in (:...pairNames)`, {
        pairNames: pairNames.map((v) => v.pairName),
      })
      .leftJoinAndSelect('playerSignup.event', 'event')
      .getMany();
    logger.info('finish searchPairList');
    return [res, count];
  }
  async searchPair(
    page: number,
    size: number,
    param: {
      categoryId: number;
      eventIds?: number[];
      playerName?: string;
    },
  ): Promise<SearchPairResponseDto> {
    logger.info('start searchPair');
    const [pairPlayerWithEventDetail, total] = await this.searchPairList(
      page,
      size,
      param,
    );
    logger.info(
      `after getPairDetail pairPlayerWithEventDetail:${pairPlayerWithEventDetail.length}`,
    );
    const playersGroupByPairName = groupBy(
      pairPlayerWithEventDetail,
      (item) => `${item.pairName}`,
    );
    const res: Array<ResponsePairDto> = Object.entries(
      playersGroupByPairName,
    ).map(([pairName, players]) => {
      const groupByEventId = groupBy(players, (v) => v.eventId);
      const events = Object.entries(groupByEventId).map(([, players]) => {
        return players[0].event;
      });
      const groupByPlayerKey = groupBy(players, (v) => v.key);
      const teamPlayer = Object.entries(groupByPlayerKey).map(([, players]) => {
        return players[0];
      });
      return {
        name: pairName,
        events: events,
        player1: teamPlayer[0] || null,
        player2: teamPlayer[1] || null,
      };
    });
    logger.info(`finish searchPair data: ${res.length}`);
    return {
      total,
      data: res,
      page,
    };
  }
  async getPlayerListDistinctByKey(
    page: number,
    size: number,
    param: {
      categoryId: number;
      eventIds?: number[];
      playerName?: string;
      type?: playerSignupTypeEnum;
      parentPlayerId?: string;
    },
  ): Promise<[Array<PlayerSignup>, number]> {
    const { categoryId, eventIds, playerName, parentPlayerId } = param;
    if (!categoryId) {
      throw new HttpException(
        'categoryId is required',
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
    const getBaseQuery = () => {
      const baseQueryBuilder = this.playerSignupRepository
        .createQueryBuilder('playerSignup')
        .select('DISTINCT playerSignup.key', 'key')
        .where('playerSignup.type = :type', {
          type: param.type || playerSignupTypeEnum.single,
        })
        .andWhere('playerSignup.key IS NOT NULL')
        .andWhere(
          parentPlayerId
            ? 'playerSignup.parentPlayerId = :parentPlayerId'
            : '1=1',
          {
            parentPlayerId,
          },
        )
        .andWhere(
          `playerSignup.eventId IN (${
            eventIds.length
              ? eventIds
              : this.eventRepository
                  .createQueryBuilder('event')
                  .select('event.id')
                  .where('event.categoryId = :categoryId')
                  .getQuery()
          })`,
        )
        .setParameters({ categoryId });
      if (playerName) {
        baseQueryBuilder.andWhere('playerSignup.name LIKE :playerName', {
          playerName: `%${playerName}%`,
        });
      }
      return baseQueryBuilder;
    };

    const { count } = await getBaseQuery()
      .select('COUNT(DISTINCT playerSignup.key)', 'count')
      .getRawOne();
    // 子查询用于去重
    const players = await getBaseQuery()
      .take(size)
      .skip((page - 1) * size)
      .orderBy('playerSignup.createTime', 'ASC')
      .getRawMany();

    return [players, parseInt(count)];
  }
  async searchPlayers(
    page: number,
    size: number,
    param: {
      categoryId: number;
      eventIds?: number[];
      playerName?: string;
      type?: playerSignupTypeEnum;
      parentPlayerId?: string;
    },
  ): Promise<SearchPlayerResponseDto> {
    const { categoryId, eventIds, playerName, type, parentPlayerId } = param;
    const [playersByPage, total] = await this.getPlayerListDistinctByKey(
      page,
      size,
      param,
    );
    if (playersByPage.length === 0) {
      return {
        total: 0,
        data: [],
        page,
      };
    }
    console.log(playersByPage.map((v) => v.id));
    const playerKeys = playersByPage.map((v) => v.key);
    const playersWithEventDetail = await this.playerSignupRepository
      .createQueryBuilder('playerSignup')
      .where('playerSignup.key IN (:...playerKeys)', { playerKeys })
      .andWhere('playerSignup.type = :type', {
        type: param.type || playerSignupTypeEnum.single,
      })
      .andWhere(
        parentPlayerId
          ? 'playerSignup.parentPlayerId = :parentPlayerId'
          : '1=1',
        {
          parentPlayerId,
        },
      )
      .andWhere(
        `playerSignup.eventId IN (${
          eventIds.length
            ? eventIds
            : this.eventRepository
                .createQueryBuilder('event')
                .select('event.id')
                .where('event.categoryId = :categoryId')
                .getQuery()
        })`,
      )
      .setParameters({ categoryId })
      .leftJoinAndSelect('playerSignup.event', 'event')
      .leftJoinAndSelect('playerSignup.subPlayerSignups', 'subPlayerSignups')
      .getMany();
    const playersGroupByKey = groupBy(playersWithEventDetail, 'key');
    const res: Array<ResponsePlayerSignupDto> = Object.entries(
      playersGroupByKey,
    ).map(([, players]) => {
      const events = (players as Array<PlayerSignup>).map((v) => v.event);
      players[0].events = events;
      players[0].event = null;
      players[0].eventId = null;
      // 如果存在subPlayer， 则需要区全集
      if (param.type === playerSignupTypeEnum.team) {
        const subPlayerSignups = [];
        const allSubPlayerSignups = (players as Array<PlayerSignup>)
          .map((v) => v.subPlayerSignups)
          .flat();
        const subPlayerSignupsGroupByKey = groupBy(allSubPlayerSignups, 'key');
        // 给subPlayer添加events
        Object.entries(subPlayerSignupsGroupByKey).forEach(([, subPlayers]) => {
          const subEventIds = (subPlayers as Array<PlayerSignup>).map(
            (v) => v.eventId,
          );
          const subEvents = events.filter((v) => subEventIds.includes(v.id));
          subPlayers[0].events = subEvents;
          subPlayerSignups.push(subPlayers[0]);
        });
        players[0].subPlayerSignups = subPlayerSignups;
      }

      return players[0];
    });
    return {
      total,
      data: res,
      page,
    };
  }
  // 校验选手是否可以报名
  canPlayerSignupEvent(player: PlayerSignup, event: Event): boolean {
    logger.info('start canPlayerSignupEvent');
    if (player.subPlayerSignups && player.subPlayerSignups.length) {
      // 创建团队赛
      if (!TeamEventList.includes(event.type)) {
        throw new HttpException(
          `${event.id} 不是团队赛，但是提供了subPlayerSignups`,
          HttpStatus.INTERNAL_SERVER_ERROR,
        );
      }
    } else {
      // 非团队赛
      if (!event || !event.category || !event.tournament) {
        logger.error(
          `Event data error ${!!event} ${!!event?.category} ${!!event?.tournament}`,
        );
        throw new HttpException(
          'Event data error',
          HttpStatus.INTERNAL_SERVER_ERROR,
        );
      }
      const { type, status } = event;
      const { maxAge, minAge } = event.category;
      const { registrationFromDate, registrationEndDate } = event.tournament;
      logger.info(
        `Event Info ... type: ${type} status: ${status} maxAge: ${maxAge} minAge: ${minAge} registrationFromDate: ${registrationFromDate} registrationEndDate: ${registrationEndDate}`,
      );
      if (
        [EventStatusEnum.Finished, EventStatusEnum.InProgress].includes(status)
      ) {
        logger.error(`Event status is ${status}, validate event status fail`);
        throw new HttpException(
          `Event status is ${status}, validate event status fail`,
          HttpStatus.INTERNAL_SERVER_ERROR,
        );
      }
      if (dayjs) {
        const now = dayjs();
        if (
          now.isBefore(registrationFromDate) ||
          now.isAfter(registrationEndDate)
        ) {
          logger.error(`validate registrationDate fail`);
          throw new HttpException(
            `validate registrationDate fail`,
            HttpStatus.INTERNAL_SERVER_ERROR,
          );
        }
      }
      const { gender, age, name } = player;
      if (!gender || !age) {
        logger.error(`player gender or age is empty`);
        throw new HttpException(
          `player gender or age is empty ${!!gender} ${!!age}`,
          HttpStatus.INTERNAL_SERVER_ERROR,
        );
      }
      const isMaleEvent = maleEventList.includes(type);
      const isFemaleEvent = femaleEventList.includes(type);
      if (
        (isMaleEvent && gender !== 'male') ||
        (isFemaleEvent && gender !== 'female')
      ) {
        logger.error(`validate gender fail`);
        throw new HttpException(
          `validate gender fail(${name} ${gender} )`,
          HttpStatus.INTERNAL_SERVER_ERROR,
        );
      }
      if (age < minAge || age > maxAge) {
        logger.error(`validate age fail`);
        throw new HttpException(
          `validate age fail(${name} ${age} )`,
          HttpStatus.INTERNAL_SERVER_ERROR,
        );
      }
    }

    logger.info(`finish canPlayerSignupEvent`);
    return true;
  }

  async addPair(createPairDto: CreatePairDto) {
    logger.info('start addPair');
    const { name, player1Key, player2Key, events } = createPairDto;
    if (!name || !player1Key || !player2Key || !events?.length) {
      throw new HttpException(
        `数据不全 ${!!name} ${!!player1Key} ${!!player2Key} ${!!events?.length}`,
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
    if (player1Key === player2Key) {
      throw new HttpException(
        `player1Key 和 player2Key 不能相同`,
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
    const eventIds = events.map((v) => v.id);
    const playersByPairName = await this.playerSignupRepository.find({
      where: {
        pairName: name,
      },
    });
    if (playersByPairName?.length) {
      throw new HttpException(
        `队名 ${name} 已经存在`,
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
    const affectedEvents = await this.eventService.findByIds(eventIds);
    if (affectedEvents.length !== events.length) {
      const missingEvents = events.filter(
        (v) => !affectedEvents.find((v2) => v2.id === v.id),
      );
      throw new HttpException(
        `部分赛事(${missingEvents.map((v) => v.id)})不存在`,
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
    const notDoubleEvents = [];
    affectedEvents.forEach((v) => {
      const { type } = v;
      if (!DoubleEventList.includes(type)) {
        notDoubleEvents.push(v);
      }
    });
    if (notDoubleEvents.length) {
      throw new HttpException(
        `部分赛事(${notDoubleEvents.map((v) => v.id)})不是双打`,
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
    const playersByKey = await this.playerSignupRepository.find({
      where: {
        key: In([player1Key, player2Key]),
        eventId: In(eventIds),
      },
    });
    const playersGroupByKey = groupBy(playersByKey, 'key');
    if (
      !playersGroupByKey[player1Key]?.length ||
      !playersGroupByKey[player2Key]?.length
    ) {
      throw new HttpException(
        `选手(${!playersGroupByKey[player1Key]?.length ? 'player1' : 'player2'})不存在`,
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
    const playersGroupByEventId = groupBy(playersByKey, 'eventId');
    Object.entries(playersGroupByEventId).forEach(([eventId, players]) => {
      if ((players as Array<PlayerSignup>).length !== 2) {
        throw new HttpException(
          `赛事(${eventId})选手人数不为2`,
          HttpStatus.INTERNAL_SERVER_ERROR,
        );
      }
    });
    const updatePlayers = playersByKey.map((v) => {
      v.pairName = name;
      return v;
    });
    logger.info(
      `player ${updatePlayers.map((v) => v.name)} will add teamName ${name}`,
    );
    await this.playerSignupRepository.save(updatePlayers);
    logger.info('finish addPair');
  }
  async updatePair(updatePairDto: UpdatePairDto) {
    const { from, to } = updatePairDto;
    logger.info('start updatePair');
    if (!from || !to) {
      throw new HttpException(
        `参数不全缺少from 或to ${!!from} ${!!to}`,
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
    if (!from.name || !to.name) {
      throw new HttpException(
        `参数不全缺少from.name 或to.name ${!!from.name} ${!!to.name}`,
        HttpStatus.INTERNAL_SERVER_ERROR,
      );
    }
    const queryRunner = this.connection.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      await this.deletePair(
        from.events.map((v) => v.id),
        from.name,
      );
      await this.addPair(to);
      await queryRunner.commitTransaction();
      logger.info('finish updatePair');
    } catch (error) {
      await queryRunner.rollbackTransaction();
      logger.error('updatePair failed, rolling back transaction', error);
      throw new HttpException('更新配对失败', HttpStatus.INTERNAL_SERVER_ERROR);
    } finally {
      await queryRunner.release();
    }
    logger.info('finish updatePair');
  }
  async deletePair(eventId: number[], pairName: string) {
    logger.info('start deletePair');
    const playersByPairName = await this.playerSignupRepository.find({
      where: {
        pairName: pairName,
        eventId: In(eventId),
      },
    });
    if (!playersByPairName.length) {
      logger.error(`队名 ${pairName} 不存在`);
    }
    const updatePlayers = playersByPairName.map((v) => {
      v.pairName = null;
      return v;
    });
    logger.info(
      `player ${updatePlayers.map((v) => v.name)} will remove teamName ${pairName}`,
    );
    await this.playerSignupRepository.save(updatePlayers);
    logger.info('finish deletePair');
  }
}
